package Main;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
import java.net.*;
import java.util.ArrayList;
import java.io.*;












/**
 *
 * @author phihai
 */



public class BroadCast {
    
    DatagramSocket utpSocket;   
    ArrayList<Networks> arr_nw = new ArrayList<Networks>();
    public BroadCast(){}
    public BroadCast(int Port)
    {
        try
        {
            utpSocket = new DatagramSocket(Port);                    
            Readmap();                      
        }
        catch (Exception e)
        {
           e.printStackTrace();
        }
    }
    
    private void send(BC_Packet pk,Networks nw ) // sned thông tin cho tất cả các client
    {      
        try
        {                              
            byte[] buf =  serialize(pk);
            DatagramPacket packet = new DatagramPacket(buf, buf.length, nw.GetAddress(), nw.GetPort());         
            utpSocket.send(packet);          
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
    
    public void sendBroadCast(BC_Packet pk) // hàm dùng để gửi thông tin pk cho các clien trong node.map
    {	         
        for(int i=0;i<arr_nw.size();i++)
        {          
            send(pk,arr_nw.get(i));
        }
    }
      
    
    private void Readmap() // hàm đọc thông tin node.map
    {
        try
        {
            String str ;
            String [] array;
            int port;
            
            BufferedReader br = new BufferedReader(new FileReader("nodes.map"));
            int count = Integer.parseInt(br.readLine());
            
            for(int i=0;i<count;i++)
            {
                str = br.readLine();
                array = str.split(" ");
                port = Integer.parseInt(array[2]);              
                arr_nw.add(new Networks(InetAddress.getByName(array[1]),port)); // arr_nw lấy thông tin IP + port
            }
            while ((str = br.readLine()) != null)
            {		                       				
                array = str.split(" "); // lấy thông tin cost -> chưa làm
                //System.out.println("Id: "+array[0] + " IP: "+array[1] +" Port: "+array[2]);	                       
            }
           
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
    
    protected void startListenerThread() // tạo cổng lắng nghe
    {        
        ReceiveRP thread = new ReceiveRP(utpSocket);
        thread.start();
    }
    
    public byte[] serialize(Object obj) throws IOException // chuyển các object thành dạng byte để tiến hành gửi thông tin
    {       
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        ObjectOutputStream objectStream = new ObjectOutputStream(byteStream);      
        objectStream.writeObject(obj);        
        objectStream.flush();        	  
        return byteStream.toByteArray();
    }
       
}
class BC_Packet implements Serializable       // lưu thông tin broadcast cần thiết cho việc gửi gói tin 
{
    private int Flag;
    private String msg;    
    private int part;
    
    private String filename;
    private byte[] data;
    private int fileSize;

    
    private long headerlength;
    private int Sequence;
    private int Acknowledge; 
    
    public BC_Packet(){}
    
    public BC_Packet(int Flag,int part,String msg)
    {
        this.Flag = Flag;
        this.part = part;
        this.msg = msg;
    }
    
    public int getFlag() {
        return Flag;
    }

    public void setFlag(int Flag) {
        this.Flag = Flag;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getPart() {
        return part;
    }

    public void setPart(int part) {
        this.part = part;
    }

    public String getFilename() {
        return filename;
    }

    public void setFilename(String filename) {
        this.filename = filename;
    }

    public byte[] getData() {
        return data;
    }

    public void setData(byte[] data) {
        this.data = data;
    }
    
  
    
    public long getHeaderlength() {
        return headerlength;
    }

    public void setHeaderlength(long headerlength) {
        this.headerlength = headerlength;
    }
    
      public int getSequence() {
        return Sequence;
    }

    public void setSequence(int Sequence) {
        this.Sequence = Sequence;
    }

    public int getAcknowledge() {
        return Acknowledge;
    }

    public void setAcknowledge(int Acknowledge) {
        this.Acknowledge = Acknowledge;
    }
    
    public int getFileSize() {
        return fileSize;
    }

    public void setFileSize(int fileSize) {
        this.fileSize = fileSize;
    }
}

class ReceiveRP extends Thread // nhận thông tin broadcast
{
    DatagramSocket socket;
    BroadCast bc = new BroadCast();
    BC_Packet pk;
    Chunks ck = new Chunks();
    byte[] Buf = new byte[1500];
    DatagramPacket Packet;
    byte [] Chunks = new byte[1024*512];
    

    
    public ReceiveRP(DatagramSocket socket)
    {
        this.socket = socket;       
    }
    public void run() 
    {                   
        while(true)
        {
            try
            { 
                
                Packet = new DatagramPacket(Buf, Buf.length);
                socket.receive(Packet);               
                pk = (BC_Packet) deserialize(Packet.getData()); // tiến hành phân giải byte ra dạng object ( cụ thể là cho ra object thuộc class BC_Packet)                
                
                if(pk.getFlag() == 0)// tìm chunks
                {
                    String name = ck.Check_Chunks(pk.getMsg());
                    pk.setFlag(1);
                    if(name.equals("")) // nếu không tìm thấy chunks
                    {
                        pk.setMsg(" ");
                    }
                    else
                    {
                        pk.setMsg(name);
                    }
                    SendPacket();
                }
                else if(pk.getFlag() == 1) 
                {
                     if(!pk.getMsg().equals(" "))
                     {                                   
                        pk.setFlag(2);
                        pk.setFilename(pk.getMsg());
                              
                     }
                     SendPacket();
                }

                else if(pk.getFlag() == 2)// read file
                {                        
                    File file = new File("Share\\"+pk.getFilename());                      
                    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
                    int size = (int) file.length();
                    byte[] buffer = new byte[1024];                 

                    int n;
                    int downloaded = 0;
                    while((n = bis.read(buffer,0,buffer.length)) != -1)
                    {
                        downloaded += n; // số lượng byte đọc được
                        pk.setSequence(pk.getAcknowledge());                     
                        pk.setAcknowledge(downloaded); 
                        pk.setFileSize(size);
                        pk.setData(buffer);
                        pk.setFlag(3);
                        System.out.println(buffer);
                        SendPacket(); // gửi packet
                    }
                    bis.close();
                    pk.setFlag(-1); // thoát khỏi quá trình truyền file
                }
                else if(pk.getFlag() == 3)// tìm chunks
                { 
                    System.out.println(pk.getFilename()+" "+pk.getSequence()+" "+pk.getAcknowledge()+" "+pk.getFileSize());
                   
                    System.arraycopy(pk.getData(),0,Chunks,pk.getSequence(),pk.getData().length); // ghép mảng pk.getdata(1024 bytes) nhận được vào mảng Chunks (512*1024)
                                                                                                                                                    
                    
                    if(pk.getAcknowledge() == pk.getFileSize())
                    {                        
                        File file = new File("Share\\"+pk.getFilename()); 
                        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
                        //out.write(Chunks,0,pk.getAcknowledge()); // tiến hành ghi file
                        out.write(Chunks,0,pk.getFileSize()); //sửa lại
                        out.flush(); 
                        out.close();                      
                    }
                    
                }   
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
    private void SendPacket()throws IOException
    {                 
        byte[] buf =  bc.serialize(pk); // chuyển object (pk)-> packet sang dạng byte 
        Packet = new DatagramPacket(buf, buf.length,Packet.getAddress(), Packet.getPort());         
        socket.send(Packet);
    }
    private Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException // hàm chuyển đổi thông tin byte nhận được sang object
    {
        ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
        ObjectInputStream objectStream = new ObjectInputStream(byteStream);
        return objectStream.readObject();
    }

}

class Networks{ // các thông tin của mạng trong file node.map
    
    private InetAddress address;
    private int port;
    private int cost; // chưa lấy thông tin cost ở đây
    
    public Networks(InetAddress address,int port)
    {
        this.address = address;
        this.port = port;        
    }
    public InetAddress GetAddress()
    {
        return address; 
    }
    public void SetAddress(InetAddress address)
    {
        this.address = address;
    }
    
    public int GetPort()
    {
        return port;
    }
    public void SetPort(int port)
    {
        this.port = port;
    }
    
    public int GetCost()
    {
        return cost;
    }
    public void SetCost(int cost)
    {
        this.cost = cost;
    }

}